/**
 * 
 */
package com.ld.tamp.web.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.ld.base.helper.RSAUtil;
import com.ld.base.support.PropertyHolder;
import com.ld.net.pfcuster.service.IPlatformCusterService;
import com.ld.net.pflogin.PlatformLoginDataRequest.D_funcl_pflogin_custerWxBinding_Request;
import com.ld.net.pflogin.PlatformLoginDataRequest.D_funcl_pflogin_isCusterWxBinding_Request;
import com.ld.net.pflogin.PlatformLoginDataRequest.D_funcl_pflogin_operatorWxBinding_Request;
import com.ld.net.pflogin.PlatformLoginDataRequest.D_funcl_pflogin_operatorWxIsBinding_Request;
import com.ld.net.pflogin.service.IPlatformLoginService;
import com.ld.net.wxb.WeixBasicDataRequest.D_funcl_wxb_client_queryWxEventMsg_Request;
import com.ld.net.wxb.WeixBasicDataRequest.D_funcl_wxb_queryWxConfig_Request;
import com.ld.net.wxb.WeixBasicDataResponse.D_funcl_wxb_client_queryWxEventMsg_Response;
import com.ld.net.wxb.WeixBasicDataResponse.D_funcl_wxb_queryWxConfig_Response;
import com.ld.net.wxb.service.IWeixBasicService;
import com.ld.tamp.web.base.WebException;
import com.ld.tamp.web.bean.AuthorizerInfo;
import com.ld.tamp.web.bean.AuthorizerJsTicket;
import com.ld.tamp.web.constant.WeiXinConstant;
import com.ld.tamp.web.controller.WeChatController;
import com.ld.tamp.web.dao.AuthorizerInfoDao;
import com.ld.tamp.web.dao.AuthorizerJsTicketDao;
import com.ld.tamp.web.dao.ComponentAccessTokenDao;
import com.qq.weixin.mp.aes.AesException;
import com.qq.weixin.mp.aes.WXBizMsgCrypt;

import weixin.popular.api.ComponentAPI;
import weixin.popular.api.MaterialAPI;
import weixin.popular.api.MediaAPI;
import weixin.popular.api.MenuAPI;
import weixin.popular.api.MessageAPI;
import weixin.popular.api.SnsAPI;
import weixin.popular.api.UserAPI;
import weixin.popular.bean.BaseResult;
import weixin.popular.bean.component.ApiQueryAuthResult;
import weixin.popular.bean.component.ApiQueryAuthResult.Authorization_info;
import weixin.popular.bean.material.MaterialBatchgetResult;
import weixin.popular.bean.material.MaterialBatchgetResultItem;
import weixin.popular.bean.material.MaterialGetResult;
import weixin.popular.bean.material.MaterialcountResult;
import weixin.popular.bean.media.MediaGetResult;
import weixin.popular.bean.menu.Button;
import weixin.popular.bean.menu.Menu;
import weixin.popular.bean.menu.MenuButtons;
import weixin.popular.bean.message.EventMessage;
import weixin.popular.bean.message.message.TextMessage;
import weixin.popular.bean.message.templatemessage.TemplateMessage;
import weixin.popular.bean.message.templatemessage.TemplateMessageItem;
import weixin.popular.bean.sns.SnsToken;
import weixin.popular.bean.user.User;
import weixin.popular.bean.xmlmessage.XMLMessage;
import weixin.popular.bean.xmlmessage.XMLNewsMessage;
import weixin.popular.bean.xmlmessage.XMLNewsMessage.Article;
import weixin.popular.bean.xmlmessage.XMLTextMessage;
import weixin.popular.support.ExpireKey;
import weixin.popular.support.expirekey.DefaultExpireKey;
import weixin.popular.util.StreamUtils;
import weixin.popular.util.XMLConverUtil;

/**
 * 微信与e站通处理服务
 * 
 * @author KeYang
 *
 */
@Service
public class WeChatService {

	private Logger logger = LoggerFactory.getLogger(WeChatController.class);

	private static String ALL_NET_TEST_USERNAME = "gh_3c884a361561";
	private static String componentToken = PropertyHolder.getProperty(WeiXinConstant.COMPONENT_TOKEN);
	private static String componentEncodingAesKey = PropertyHolder.getProperty(WeiXinConstant.COMPONENT_ENCODING_AES_KEY);
	private static String componentAppid = PropertyHolder.getProperty(WeiXinConstant.COMPONENT_APPID);
	private static String state = "20170626";
	public static final String PATH_CLASS_ROOT = WeChatService.class.getClassLoader().getResource("").getPath();
	public static final String ROOT_PATH = PATH_CLASS_ROOT.substring(0, PATH_CLASS_ROOT.length() - "WEB-INF/classes/".length());
	
	

	// 重复通知过滤
	private static ExpireKey expireKey = new DefaultExpireKey();

	private static Map<String, D_funcl_wxb_queryWxConfig_Response> cacheWxConfigs = new ConcurrentHashMap<>();

	private static Map<String, Map<Long, SnsToken>> cacheUserTokenMap = new LinkedHashMap<String, Map<Long, SnsToken>>(); //appid - (cust_no - token)

	private static Map<String, String> cacheThumbs = new LinkedHashMap<String, String>();

	@Autowired
	IWeixBasicService wxservice;
	@Autowired
	IPlatformLoginService pfLoginService;
	@Autowired
	IPlatformCusterService pfCusterService;
	@Autowired
	private ComponentAccessTokenDao componentAccessTokenDao;
	@Autowired
	private AuthorizerInfoDao authorizerInfoDao;
	@Autowired
	private AuthorizerJsTicketDao authorizerJsTicketDao;
	

	/**
	 * 数据流输出
	 * 
	 * @param outputStream
	 * @param text
	 * @return
	 */

	private boolean outputStreamWrite(OutputStream outputStream, String text) {
		try {
			outputStream.write(text.getBytes("utf-8"));
			outputStream.flush();
			outputStream.close();
		} catch (UnsupportedEncodingException e) {
			logger.error("不支持的字符格式", e);
			return false;
		} catch (IOException e) {
			logger.error("发送数据失败", e);
			return false;
		}
		return true;
	}

	/**
	 * 处理事件消息
	 * 
	 * @param outputStream
	 * @param wxBizMsgCrypt
	 * @param eventMessage
	 * @throws IOException
	 */
	public void processMessageAndEvent(HttpServletRequest request, HttpServletResponse response) throws IOException {

		String timestamp = request.getParameter("timestamp");
		String nonce = request.getParameter("nonce");
		String msgSignature = request.getParameter("msg_signature");

		WXBizMsgCrypt wxBizMsgCrypt = null;
		EventMessage eventMessage = null;
		ServletOutputStream outputStream = response.getOutputStream();
		try {
			wxBizMsgCrypt = new WXBizMsgCrypt(componentToken, componentEncodingAesKey, componentAppid);
			String postData = StreamUtils.copyToString(request.getInputStream(), Charset.forName("utf-8"));
			String xmlData = wxBizMsgCrypt.decryptMsg(msgSignature, timestamp, nonce, postData);
			eventMessage = XMLConverUtil.convertToObject(EventMessage.class, xmlData);
		} catch (AesException e) {
			e.printStackTrace();
		}
		if (eventMessage != null) {

			String key = eventMessage.getFromUserName() + "__" + eventMessage.getToUserName() + "__" + eventMessage.getMsgId() + "__" + eventMessage.getCreateTime();

			if (expireKey.exists(key)) {
				// 重复通知不作处理
				return;
			} else {
				expireKey.add(key);
			}
			// 微信全网测试账号
			if (StringUtils.equalsIgnoreCase(eventMessage.getToUserName(), ALL_NET_TEST_USERNAME)) {
				logger.info("-----------全网发布接入检测开始--------------");
				allNetPublishCheck(outputStream, wxBizMsgCrypt, eventMessage);
			} else {
				//正式环境处理逻辑
				processMessageAndEvent(outputStream, wxBizMsgCrypt, eventMessage);
			}
		}
	}

	/**
	 * * 处理事件消息 
	 */
	private void processMessageAndEvent(OutputStream outputStream, WXBizMsgCrypt wxBizMsgCrypt, EventMessage eventMessage) {
		switch (eventMessage.getMsgType()) {
		case "event":
			String event = eventMessage.getEvent();
			logger.info("接收到公众号转发事件 event:{}", event);
			XMLMessage result;
			if ("click".equalsIgnoreCase(event)) {
				logger.info("菜单点击，eventKey:{}", eventMessage.getEventKey());
				D_funcl_wxb_client_queryWxEventMsg_Request request = new D_funcl_wxb_client_queryWxEventMsg_Request();
				request.setEvent_key(eventMessage.getEventKey());
				request.setRow_count(-1L);
				List<D_funcl_wxb_client_queryWxEventMsg_Response> list = wxservice.funcl_wxb_client_queryWxEventMsg(request);
				if (list == null || list.isEmpty()) {
					result = new XMLTextMessage(eventMessage.getFromUserName(), eventMessage.getToUserName(), "抱歉没有相关信息");
				} else {
					D_funcl_wxb_client_queryWxEventMsg_Response wxEventMsg = list.get(0);
					if(WeiXinConstant.EVENT_MSG_TYPE_NEWS.equals(wxEventMsg.getEvent_msg_type())){
						List<Article> articles = new ArrayList<>();
						for (D_funcl_wxb_client_queryWxEventMsg_Response wxArticle : list) {
							Article article = new Article();
							article.setDescription(wxArticle.getArticle_describe());
							article.setPicurl(wxArticle.getImg_url());
							article.setTitle(wxArticle.getArticle_title());
							article.setUrl(wxArticle.getArticle_url());
							articles.add(article);
						}
						result = new XMLNewsMessage(eventMessage.getFromUserName(), eventMessage.getToUserName(), articles);
					}else{
						result = new XMLTextMessage(eventMessage.getFromUserName(),eventMessage.getToUserName(),wxEventMsg.getEvent_msg_content());
					}
				}
				result.outputStreamWrite(outputStream, wxBizMsgCrypt);
			}else if("TEMPLATESENDJOBFINISH".equalsIgnoreCase(event)){
				logger.info("模板消息发送结果,MsgID:{},Status:{}", eventMessage.getMsgId(),eventMessage.getStatus());
			}else {
				outputStreamWrite(outputStream, "success");
			}
			break;
		default:
			outputStreamWrite(outputStream, "success");
			break;
		}
	}

	/**
	 * 全网发布
	 * 
	 * @param outputStream
	 * @param wxBizMsgCrypt
	 * @param eventMessage
	 */
	private void allNetPublishCheck(OutputStream outputStream, WXBizMsgCrypt wxBizMsgCrypt, EventMessage eventMessage) {

		String componentAppid = PropertyHolder.getProperty(WeiXinConstant.COMPONENT_APPID);

		if ("event".equals(eventMessage.getMsgType())) {
			String event = eventMessage.getEvent();
			logger.info("全网发布接入 接收到事件消息--------------event=" + event);
			XMLTextMessage replyMessage = new XMLTextMessage(eventMessage.getFromUserName(), eventMessage.getToUserName(), event + "from_callback");
			replyMessage.outputStreamWrite(outputStream, wxBizMsgCrypt);
		} else if ("text".equals(eventMessage.getMsgType())) {
			String content = eventMessage.getContent();
			logger.info("全网发布接入 接收到文字消息--------------content=" + content);
			if ("TESTCOMPONENT_MSG_TYPE_TEXT".equals(content)) {
				XMLTextMessage replyMessage = new XMLTextMessage(eventMessage.getFromUserName(), eventMessage.getToUserName(), "TESTCOMPONENT_MSG_TYPE_TEXT_callback");
				replyMessage.outputStreamWrite(outputStream, wxBizMsgCrypt);
			} else if (content.startsWith("QUERY_AUTH_CODE")) {
				logger.info("需要返回API文本消息——--------------");
				outputStreamWrite(outputStream, "");
				String authCode = content.split(":")[1];
				TextMessage replyMessage = new TextMessage(eventMessage.getFromUserName(), authCode + "_from_api");
				String componentAccessToken = componentAccessTokenDao.read().getComponentAccessToken();
				if (componentAccessToken != null) {
					ApiQueryAuthResult queryAuthResult = ComponentAPI.api_query_auth(componentAccessToken, componentAppid, authCode);
					if (queryAuthResult.isSuccess()) {
						Authorization_info authorizationInfo = queryAuthResult.getAuthorization_info();
						String authorizer_access_token = authorizationInfo.getAuthorizer_access_token();
						MessageAPI.messageCustomSend(authorizer_access_token, replyMessage);
					}
				}
			}
		}

	}

	/**
	 * 获得当前机构的微信配置
	 * 
	 * @param co_no
	 * @param app_id
	 *            这个有可能传的无效
	 * @return
	 */
	public D_funcl_wxb_queryWxConfig_Response getWxConfig(Long co_no, String app_id) {
		logger.info("获取微信配置，app_id:{}", app_id);
		D_funcl_wxb_queryWxConfig_Request request = new D_funcl_wxb_queryWxConfig_Request();
		request.setRow_count(-1L);
		request.setCo_no(co_no);
		// request.setApp_id(app_id);
		List<D_funcl_wxb_queryWxConfig_Response> list = wxservice.funcl_wxb_queryWxConfig(request);
		if (CollectionUtils.isNotEmpty(list)) {
			for (D_funcl_wxb_queryWxConfig_Response config : list) {
				cacheWxConfigs.put(config.getApp_id(), config);
			}
			if (StringUtils.isNotEmpty(app_id) && cacheWxConfigs.containsKey(app_id)) {
				return cacheWxConfigs.get(app_id);
			} else {
				return list.get(0);
			}
		}
		return null;
	}

	/**
	 * 生成代机构授权绑定url
	 * 
	 * @param co_no
	 * @param redirectUrl
	 * @param appid
	 * @return
	 */
	public String generateWeChatBindUrl(Long co_no, String appid, String redirectUrl) {
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no, appid);
		boolean snsapi_userinfo = true;
		String url = "";
		//只有服务号才能绑定登录，否则直接返回bindback页面
		if (wxConfigResponse != null && WeiXinConstant.SERVICE_TYPE_2.equals(wxConfigResponse.getService_type())
				&& WeiXinConstant.VERIFY_TYPE_0.equals(wxConfigResponse.getVerify_type())) {
			appid = wxConfigResponse.getApp_id();
			url = SnsAPI.connectOauth2Authorize(appid, redirectUrl, snsapi_userinfo, state, componentAppid);
		}else{
			url = "http://weixin.ldtamp.com/wechat/bind/callback?co_no="+co_no;
		}
		return url;
	}

	
	/**
	 * 绑定用户信息
	 * @param co_no
	 * @param cust_no 可能为null
	 * @param app_id 可能传过来的是无效的
	 * @param code
	 * @param returnUrl
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public String bindCuster(Long co_no, Long cust_no, String app_id, String code, String returnUrl) throws UnsupportedEncodingException {
		logger.info("绑定用户,co_no:{},cust_no:{},app_id:{},code:{}",co_no,cust_no,app_id,code);
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no,app_id);
		String appid = "";
		String openid = "";
		String component_access_token = componentAccessTokenDao.read().getComponentAccessToken();
		String result = "";
		String ticket = "";
		SnsToken snsToken;
		if (wxConfigResponse != null) {
			appid = wxConfigResponse.getApp_id();
				//通过code换取网页授权access_token
				snsToken = SnsAPI.oauth2ComponentAccessToken(appid, code, componentAppid, component_access_token);
				if (snsToken.isSuccess()) {
					openid = doBindCuster(co_no, cust_no, appid, component_access_token,snsToken);
				}
			ticket = authorizerJsTicketDao.read(appid).getTicket();
		}
		logger.info("返回open_id：{},ticket：{}", openid,ticket);
		try {
			result = generateReturnUrl(appid,openid,ticket,returnUrl);
		} catch (Throwable throwable) {
			throwable.printStackTrace();
		}
		return result;
	}

	/**
	 * 登录用户信息
	 * @param co_no
	 * @param app_id 可能传过来的是无效的
	 * @param code
	 * @param returnUrl
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public String loginCuster(Long co_no,String app_id, String code, String returnUrl) throws UnsupportedEncodingException {
		logger.info("登录用户,co_no:{},app_id:{},code:{}",co_no,app_id,code);
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no,app_id);
		String appid = "";
		String openid = "";
		String component_access_token = componentAccessTokenDao.read().getComponentAccessToken();
		String result = "";
		String ticket = "";
		if (wxConfigResponse != null) {
			appid = wxConfigResponse.getApp_id();
			openid = queryCusterBindInfo(co_no, appid, code, component_access_token);
			ticket = authorizerJsTicketDao.read(appid).getTicket();
		}
		logger.info("返回open_id：{},ticket：{}", openid,ticket);
		try {
			result = generateReturnUrl(appid,openid,ticket,returnUrl);
		} catch (Throwable throwable) {
			throwable.printStackTrace();
		}
		return result;
	}

	protected String generateReturnUrl(String appid, String openid, String ticket,String returnUrl) throws Throwable {
		String openidStr = "";
		String ticketStr = "";
		if(openid!=null){
			openidStr = RSAUtil.encodePublicKey(PropertyHolder.getProperty("openid.pubKey"), openid);
			openidStr = URLEncoder.encode(openidStr, "utf-8");
		}
		if(ticket!=null){
			ticketStr = RSAUtil.encodePublicKey(PropertyHolder.getProperty("openid.pubKey"), ticket);
			ticketStr = URLEncoder.encode(ticketStr, "utf-8");
		}
		return returnUrl.replace("{APPID}",appid).replace("{OPENID}", openidStr).replace("{TICKET}", ticketStr);
	}

	public String generateWxJsConfig(Long co_no, String app_id, String returnUrl) throws UnsupportedEncodingException{
		String result = "";
		String appid = "";
		String ticket = "";
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no,app_id);
		if (wxConfigResponse != null) {
			appid = wxConfigResponse.getApp_id();
			AuthorizerJsTicket autherizerJsTicket = authorizerJsTicketDao.read(appid);
//			String configJson = JsUtil.generateConfigJson(autherizerJsTicket.getTicket(), true, appid, "", jsApiList);
			String rsaStr = RSAUtil.encodePublicKey(PropertyHolder.getProperty("openid.pubKey"), autherizerJsTicket.getTicket());
			ticket = URLEncoder.encode(rsaStr, "utf-8");
		}
		result = returnUrl.replace("{APPID}",appid).replace("{TICKET}", ticket);
		return result;
	}

	/**
	 * @param co_no
	 * @param access_token
	 * @return openid
	 */
	private String queryCusterBindInfo(Long co_no, String appid, String code, String component_access_token) {

		D_funcl_pflogin_isCusterWxBinding_Request isBindReq;
		SnsToken snsToken = SnsAPI.oauth2ComponentAccessToken(appid, code, componentAppid, component_access_token);
		if(snsToken.isSuccess()){
			try {
				isBindReq = new D_funcl_pflogin_isCusterWxBinding_Request();
				isBindReq.setCo_no(co_no);
				isBindReq.setWx_open_id(snsToken.getOpenid());
				pfLoginService.funcl_pflogin_isCusterWxBinding(isBindReq);
			} catch (Exception e) {
				logger.error("未绑定,新用户,openid为：{}", snsToken.getOpenid());
			}
		}else{
			logger.info("无法获取用户信息,{}",snsToken.getErrmsg());
			return null;
		}
		return snsToken.getOpenid();
	}
	
	/**
	 * 查询操作员绑定信息
	 * @param co_no
	 * @param access_token
	 * @return openid
	 */
	private String queryOporBindInfo(Long co_no, String appid, String code, String component_access_token) {

		D_funcl_pflogin_operatorWxIsBinding_Request isBindReq;
		SnsToken snsToken = SnsAPI.oauth2ComponentAccessToken(appid, code, componentAppid, component_access_token);
		if(snsToken.isSuccess()){
			try {
				isBindReq = new D_funcl_pflogin_operatorWxIsBinding_Request();
				isBindReq.setCo_no(co_no);
				isBindReq.setOpor_co_no(co_no);
				isBindReq.setWx_open_id(snsToken.getOpenid());
				pfLoginService.funcl_pflogin_operatorWxIsBinding(isBindReq);
			} catch (Exception e) {
				logger.error("未绑定,新用户,openid为：{}", snsToken.getOpenid());
			}
		}else{
			logger.info("无法获取用户信息,{}",snsToken.getErrmsg());
			return null;
		}
		return snsToken.getOpenid();
	}

	/**
	 * @param co_no
	 * @param cust_no
	 * @param appid
	 * @param component_access_token
	 * @param snsToken 
	 * @param snsToken
	 * @return
	 */
	private String doBindCuster(Long co_no, Long cust_no, String appid, String component_access_token, SnsToken snsToken) {
		
		Map<Long, SnsToken> userAccessTokenMap = cacheUserTokenMap.getOrDefault(appid, new LinkedHashMap<Long, SnsToken>());
		
		String openid = snsToken.getOpenid();
		D_funcl_pflogin_isCusterWxBinding_Request isBindReq;
		D_funcl_pflogin_custerWxBinding_Request bindRequest;
		String access_token = snsToken.getAccess_token();
		logger.info("获取用户信息的access_tokne:{}", access_token);
		try {
			isBindReq = new D_funcl_pflogin_isCusterWxBinding_Request();
			isBindReq.setCo_no(co_no);
			isBindReq.setWx_open_id(openid);
			pfLoginService.funcl_pflogin_isCusterWxBinding(isBindReq);
			logger.info("用户已绑定,直接返回");
		} catch (Exception e) {
			logger.error("用户未绑定", e);
			User user = SnsAPI.userinfo(access_token, openid, "zh_CN");
			if (user.isSuccess()) {
				String headimgUrl = user.getHeadimgurl();
				bindRequest = new D_funcl_pflogin_custerWxBinding_Request();
				bindRequest.setCo_no(co_no);
				bindRequest.setApp_id(appid);
				bindRequest.setCust_no(cust_no);
				bindRequest.setWx_head(headimgUrl);
				bindRequest.setWx_open_id(openid);
				pfLoginService.funcl_pflogin_custerWxBinding(bindRequest);
				logger.info("微信用户绑定成功");
			} else {
				logger.error("获取用户信息失败,失败原因：{}", user.getErrmsg());
				if ("40001".equals(user.getErrcode())) {
					//重新拉去用户信息调用权限token
					snsToken = SnsAPI.oauth2ComponentRefreshToken(appid, snsToken.getRefresh_token(), componentAppid, component_access_token);
					if (cust_no != null) {
						userAccessTokenMap.put(cust_no, snsToken);
						cacheUserTokenMap.put(appid, userAccessTokenMap);
					}
					access_token = snsToken.getAccess_token();
					logger.info("获取用户信息的access_tokne:{}", access_token);
					user = UserAPI.userInfo(access_token, openid);
					String headimgUrl = user.getHeadimgurl();
					bindRequest = new D_funcl_pflogin_custerWxBinding_Request();
					bindRequest.setCo_no(co_no);
					bindRequest.setCust_no(cust_no);
					bindRequest.setApp_id(appid);
					bindRequest.setWx_head(headimgUrl);
					bindRequest.setWx_open_id(openid);
					pfLoginService.funcl_pflogin_custerWxBinding(bindRequest);
					logger.info("微信用户绑定成功");
				}
			}
		}
		return openid;
	}

	/**
	 * 绑定操作员
	 * @param co_no
	 * @param opor_no
	 * @param appid
	 * @param component_access_token
	 * @param snsToken
	 * @return
	 */
	private String doBindOpor(Long co_no, Long opor_no, String appid, String component_access_token, SnsToken snsToken) {

		Map<Long, SnsToken> userAccessTokenMap = cacheUserTokenMap.getOrDefault(appid, new LinkedHashMap<Long, SnsToken>());

		String openid = snsToken.getOpenid();
		D_funcl_pflogin_operatorWxIsBinding_Request isBindReq;
		D_funcl_pflogin_operatorWxBinding_Request bindRequest;
		String access_token = snsToken.getAccess_token();
		logger.info("获取操作员的access_tokne:{}", access_token);
		try {
			isBindReq = new D_funcl_pflogin_operatorWxIsBinding_Request();
			isBindReq.setCo_no(co_no);
			isBindReq.setWx_open_id(openid);
			pfLoginService.funcl_pflogin_operatorWxIsBinding(isBindReq);
			logger.info("操作员已绑定,直接返回");
		} catch (Exception e) {
			logger.info("操作员未绑定,开始绑定。。。");
			User user = SnsAPI.userinfo(access_token, openid, "zh_CN");
			if (user.isSuccess()) {
				String headimgUrl = user.getHeadimgurl();
				bindRequest = new D_funcl_pflogin_operatorWxBinding_Request();
				bindRequest.setCo_no(co_no);
				bindRequest.setOpor_co_no(co_no);
				bindRequest.setApp_id(appid);
				bindRequest.setOpor_no(opor_no);
				bindRequest.setWx_head(headimgUrl);
				bindRequest.setWx_open_id(openid);
				try{
					pfLoginService.funcl_pflogin_operatorWxBinding(bindRequest);
				}catch(Exception e1){
					logger.error("绑定操作员失败",e1);
				}
				logger.info("操作员绑定成功");
			} else {
				logger.error("获取用户信息失败,失败原因:{}", user.getErrmsg());
				if ("40001".equals(user.getErrcode())) {
					//重新拉去用户信息调用权限token
					snsToken = SnsAPI.oauth2ComponentRefreshToken(appid, snsToken.getRefresh_token(), componentAppid, component_access_token);
					if (opor_no != null) {
						userAccessTokenMap.put(opor_no, snsToken);
						cacheUserTokenMap.put(appid, userAccessTokenMap);
					}
					access_token = snsToken.getAccess_token();
					logger.info("获取用户信息的access_tokne:{}", access_token);
					user = UserAPI.userInfo(access_token, openid);
					String headimgUrl = user.getHeadimgurl();
					bindRequest = new D_funcl_pflogin_operatorWxBinding_Request();
					bindRequest.setCo_no(co_no);
					bindRequest.setOpor_no(opor_no);
					bindRequest.setApp_id(appid);
					bindRequest.setWx_head(headimgUrl);
					bindRequest.setWx_open_id(openid);
					try{
						pfLoginService.funcl_pflogin_operatorWxBinding(bindRequest);
					}catch(Exception e1){
						logger.error("绑定操作员失败",e1);
					}
					logger.info("操作员绑定成功");
				}
			}
		}
		return openid;
	}

	/**
	 * 获取菜单列表
	 * 
	 * @param co_no
	 * @return
	 */
	public Menu getMenuList(Long co_no,String app_id) {
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no, app_id);
		String appid = "";
		Menu menu = null;
		if (wxConfigResponse != null) {
			appid = wxConfigResponse.getApp_id();
		} else {
			throw new WebException("400004", "机构没有授权");
		}
		AuthorizerInfo authorizerInfo = authorizerInfoDao.read(appid);
		if (authorizerInfo != null) {
			String access_token = authorizerInfo.getAuthorizer_access_token();
			menu = MenuAPI.menuGet(access_token);
		}
		return menu;
	}

	/**
	 * 创建菜单
	 * 
	 * @param co_no
	 * @param menuJson
	 * @param menuJson2 
	 * @return
	 */
	public BaseResult createMenu(Long co_no, String app_id, String menuJson) {
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no, app_id);
		String appid = "";
		BaseResult baseResult = null;
		if (wxConfigResponse != null) {
			appid = wxConfigResponse.getApp_id();
		} else {
			throw new WebException("400004", "机构编号不能为空");
		}
		AuthorizerInfo authorizerInfo = authorizerInfoDao.read(appid);
		if (authorizerInfo != null) {
			String access_token = authorizerInfo.getAuthorizer_access_token();
			MenuButtons menuButtons = JSON.parseObject(menuJson, MenuButtons.class);
			for (Button button : menuButtons.getButton()) {
				if (button.getSub_button() != null && !button.getSub_button().isEmpty()) {
					for (Button subButton : button.getSub_button()) {
						if ("login".equals(subButton.getType())) {
							subButton.setUrl("http://" + appid + ".wx.ldtamp.com/login.do");
							subButton.setType("view");
						}
					}
				} else {
					if ("login".equals(button.getType())) {
						button.setUrl("http://" + appid + ".wx.ldtamp.com/login.do");
						button.setType("view");
					}
				}
			}
			baseResult = MenuAPI.menuCreate(access_token, menuButtons);
		}

		return baseResult;
	}

	/**
	 * 获得素材数量
	 * 
	 * @param co_no
	 * @param app_id 
	 * @return
	 */
	public MaterialcountResult getMaterialCount(Long co_no, String app_id) {
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no, app_id);
		String appid = "";
		MaterialcountResult result = null;
		if (wxConfigResponse != null) {
			appid = wxConfigResponse.getApp_id();
		} else {
			throw new WebException("400004", "无法找到该机构公众号");
		}
		AuthorizerInfo authorizerInfo = authorizerInfoDao.read(appid);
		if (authorizerInfo != null) {
			String access_token = authorizerInfo.getAuthorizer_access_token();
			result = MaterialAPI.get_materialcount(access_token);
		}
		return result;
	}

	/**
	 * 获得永久素材
	 * 
	 * @param co_no
	 * @return
	 * @throws IOException
	 */
	public MaterialBatchgetResult getMaterialList(Long co_no, String app_id ,int start, int rowCount) throws IOException {
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no, app_id);
		String appid = "";
		MaterialBatchgetResult result = null;
		if (wxConfigResponse != null) {
			appid = wxConfigResponse.getApp_id();
		} else {
			throw new WebException("400004", "机构编号不能为空");
		}
		AuthorizerInfo authorizerInfo = authorizerInfoDao.read(appid);
		if (authorizerInfo != null) {
			String access_token = authorizerInfo.getAuthorizer_access_token();
			result = MaterialAPI.batchget_material(access_token, "news", start, rowCount);
			if (result.isSuccess()) {
				List<MaterialBatchgetResultItem> itemList = result.getItem();
				for (MaterialBatchgetResultItem item : itemList) {
					List<weixin.popular.bean.message.Article> articleList = item.getContent().getNews_item();
					for (weixin.popular.bean.message.Article article : articleList) {
						String thumbMediaId = article.getThumb_media_id();
						downloadThumbPicture(co_no, access_token, thumbMediaId);
					}
				}
			}
		}
		return result;
	}

	

	/**
	 * 下载缩略图
	 * 
	 * @param co_no
	 * @param thumbMediaId
	 * @throws IOException
	 */
	private void downloadThumbPicture(Long co_no, String access_token, String thumbMediaId) throws IOException {
		if (cacheThumbs.containsKey(thumbMediaId)) {
			return;
		}
		MaterialGetResult result = MaterialAPI.get_material(access_token, thumbMediaId);
		if (result.isSuccess()) {
			byte[] bytes = result.getBytes();
			String folder = ROOT_PATH + "/thumb/" + co_no;
			File folderFile = new File(folder);
			if (!folderFile.exists()) {
				folderFile.mkdirs();
			}
			File file = new File(folderFile, thumbMediaId + ".jpg");
			if (!file.exists()) {
				file.createNewFile();
			}
			FileOutputStream outputStream = new FileOutputStream(file);
			try {
				outputStream.write(bytes);
				cacheThumbs.put(thumbMediaId, file.getAbsolutePath());
			} catch (Exception e) {
				logger.error("保存文件出错。。。");
			} finally {
				outputStream.flush();
				outputStream.close();
			}
		}

	}

	/**
	 * 新增永久素材
	 * 
	 * @param co_no
	 * @return
	 */
	public BaseResult addMaterialNews(Long co_no, String app_id, List<weixin.popular.bean.message.Article> articles) {
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no, app_id);
		String appid = "";
		BaseResult result = null;
		if (wxConfigResponse != null) {
			appid = wxConfigResponse.getApp_id();
		} else {
			throw new WebException("400004", "机构编号不能为空");
		}
		AuthorizerInfo authorizerInfo = authorizerInfoDao.read(appid);
		if (authorizerInfo != null) {
			String access_token = authorizerInfo.getAuthorizer_access_token();
			result = MaterialAPI.add_news(access_token, articles);
		}
		return result;
	}

	/**
	 * @param co_no
	 * @return
	 */
	public BaseResult updateMaterialNews(Long co_no, String app_id, String media_id, int index, List<weixin.popular.bean.message.Article> articles) {
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no, app_id);
		String appid = "";
		BaseResult result = null;
		if (wxConfigResponse != null) {
			appid = wxConfigResponse.getApp_id();
		} else {
			throw new WebException("400004", "机构编号不能为空");
		}
		AuthorizerInfo authorizerInfo = authorizerInfoDao.read(appid);
		if (authorizerInfo != null) {
			String access_token = authorizerInfo.getAuthorizer_access_token();
			result = MaterialAPI.update_news(access_token, media_id, index, articles);
		}
		return result;
	}
	 
	public BaseResult sendMessageTemplate(Long co_no, String app_id,String template_id,String touser,String url,LinkedHashMap<String, TemplateMessageItem> data){
		BaseResult result = null;
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no, app_id);
		String appid = "";
		TemplateMessage templateMessage = null;
		if (wxConfigResponse != null && WeiXinConstant.SERVICE_TYPE_2.equals(wxConfigResponse.getService_type())
				&& WeiXinConstant.VERIFY_TYPE_0.equals(wxConfigResponse.getVerify_type())) {
			appid = wxConfigResponse.getApp_id();
		} else {
			throw new WebException("400004", "微信公众号没有发送消息模板的权限,co_no:"+co_no+"app_id:"+app_id);
		}
		AuthorizerInfo authorizerInfo = authorizerInfoDao.read(appid);
		if (authorizerInfo != null) {
			String access_token = authorizerInfo.getAuthorizer_access_token();
			templateMessage = new TemplateMessage();
			templateMessage.setTemplate_id(template_id);
			templateMessage.setTouser(touser);
			templateMessage.setData(data);
			templateMessage.setUrl(url);
			result =  MessageAPI.messageTemplateSend(access_token, templateMessage);
		}
		return result;
	}
	
	public BaseResult getMedia(Long co_no,String app_id,String media_id){
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no,app_id);
		MediaGetResult result = null;
		AuthorizerInfo authorizerInfo = null;
		if (wxConfigResponse != null) {
			String appid = wxConfigResponse.getApp_id();
			authorizerInfo = authorizerInfoDao.read(appid);
			result = MediaAPI.mediaGet(authorizerInfo.getAuthorizer_access_token(), media_id);
		}
		return result;
	}

	/**
	 * 绑定操作员
	 * @param co_no
	 * @param code
	 * @param returnUrl
	 * @return
	 */
	public String bindOpor(Long co_no, Long opor_no, String app_id, String code, String returnUrl) {
		logger.info("绑定操作员,co_no:{},opor_no:{},app_id:{},code:{}",co_no,opor_no,app_id,code);
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no,app_id); //为了保证传进来的app_id是真实有效的
		String openid = "";
		String component_access_token = componentAccessTokenDao.read().getComponentAccessToken();
		String result = "";
		String ticket = "";
		SnsToken snsToken;
		if (wxConfigResponse!=null){
			app_id = wxConfigResponse.getApp_id(); // 因为app_id传进来的可能是假的
			snsToken = SnsAPI.oauth2ComponentAccessToken(app_id, code, componentAppid, component_access_token);
			if (snsToken.isSuccess()) {
				openid = doBindOpor(co_no, opor_no, app_id, component_access_token,snsToken);
			}
			ticket = authorizerJsTicketDao.read(app_id).getTicket();
		}
		logger.info("返回open_id：{},ticket：{}", openid,ticket);
		try {
			result = generateReturnUrl(app_id,openid,ticket,returnUrl);
		} catch (Throwable throwable) {
			throwable.printStackTrace();
		}
		return result;
	}

	public String loginOpor(Long co_no, String app_id, String code, String returnUrl) {
		logger.info("登录操作员,co_no:{},app_id:{},code:{}",co_no,app_id,code);
		D_funcl_wxb_queryWxConfig_Response wxConfigResponse = getWxConfig(co_no,app_id);
		String openid = "";
		String component_access_token = componentAccessTokenDao.read().getComponentAccessToken();
		String result = "";
		String ticket = "";
		if (wxConfigResponse != null) {
			app_id = wxConfigResponse.getApp_id();
			openid = queryOporBindInfo(co_no, app_id, code, component_access_token);
			ticket = authorizerJsTicketDao.read(app_id).getTicket();
		}
		logger.info("返回open_id:{},ticket:{}", openid,ticket);
		try {
			result = generateReturnUrl(app_id,openid,ticket,returnUrl);
		} catch (Throwable throwable) {
			throwable.printStackTrace();
		}
		return result;
	}
}
